/*
 * Copyright (c) Tony Bybell 2005-2014.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 */

#include <config.h>
#include "globals.h"
#include "vcd_saver.h"
#include <time.h>

static void w32redirect_fprintf(int is_trans, FILE *sfd, const char *format, ...)
    G_GNUC_PRINTF(3, 4);

static void w32redirect_fprintf(int is_trans, FILE *sfd, const char *format, ...)
{
#if defined __MINGW32__
    if (is_trans) {
        char buf[16385];
        BOOL bSuccess;
        DWORD dwWritten;

        va_list ap;
        va_start(ap, format);
        buf[0] = 0;
        vsprintf(buf, format, ap);
        bSuccess = WriteFile((HANDLE)sfd, buf, strlen(buf), &dwWritten, NULL);
        va_end(ap);
    } else
#else
    (void)is_trans;
#endif
    {
        va_list ap;
        va_start(ap, format);
        vfprintf(sfd, format, ap);
        va_end(ap);
    }
}

/*
 * unconvert trace data back to VCD representation...use strict mode for LXT
 */
static unsigned char analyzer_demang(int strict, unsigned char ch)
{
    if (!strict) {
        if (ch < GW_BIT_COUNT) {
            return gw_bit_to_char(ch);
        } else {
            return (ch);
        }
    } else {
        if (ch < GW_BIT_COUNT) {
            return (AN_STR4ST[ch]);
        } else {
            return (ch);
        }
    }
}

/*
 * generate a vcd identifier for a given facindx
 */
static char *vcdid(unsigned int value, int export_typ)
{
    char *pnt = GLOBALS->buf_vcd_saver_c_3;
    unsigned int vmod;

    if (export_typ != WAVE_EXPORT_TRANS) {
        value++;
        for (;;) {
            if ((vmod = (value % 94))) {
                *(pnt++) = (char)(vmod + 32);
            } else {
                *(pnt++) = '~';
                value -= 94;
            }
            value = value / 94;
            if (!value) {
                break;
            }
        }

        *pnt = 0;
    } else {
        sprintf(pnt, "%d", value);
    }

    return (GLOBALS->buf_vcd_saver_c_3);
}

static char *vcd_truncate_bitvec(char *s)
{
    char l, r;

    r = *s;
    if (r == '1') {
        return s;
    } else {
        s++;
    }

    for (;; s++) {
        l = r;
        r = *s;
        if (!r)
            return (s - 1);

        if (l != r) {
            return (((l == '0') && (r == '1')) ? s : s - 1);
        }
    }
}

/************************ splay ************************/

/*
 * integer splay
 */
typedef struct vcdsav_tree_node vcdsav_Tree;
struct vcdsav_tree_node
{
    vcdsav_Tree *left, *right;
    GwNode *item;
    int val;
    GwHistEnt *hist;
    int len;

    union
    {
        void *p;
        long l;
        int i;
    } handle; /* future expansion for adding other writers that need pnt/handle info */

    unsigned char flags;
};

static long vcdsav_cmp_l(void *i, void *j)
{
    intptr_t il = (intptr_t)i, jl = (intptr_t)j;
    return (il - jl);
}

static vcdsav_Tree *vcdsav_splay(void *i, vcdsav_Tree *t)
{
    /* Simple top down splay, not requiring i to be in the tree t.  */
    /* What it does is described above.                             */
    vcdsav_Tree N, *l, *r, *y;
    int dir;

    if (t == NULL)
        return t;
    N.left = N.right = NULL;
    l = r = &N;

    for (;;) {
        dir = vcdsav_cmp_l(i, t->item);
        if (dir < 0) {
            if (t->left == NULL)
                break;
            if (vcdsav_cmp_l(i, t->left->item) < 0) {
                y = t->left; /* rotate right */
                t->left = y->right;
                y->right = t;
                t = y;
                if (t->left == NULL)
                    break;
            }
            r->left = t; /* link right */
            r = t;
            t = t->left;
        } else if (dir > 0) {
            if (t->right == NULL)
                break;
            if (vcdsav_cmp_l(i, t->right->item) > 0) {
                y = t->right; /* rotate left */
                t->right = y->left;
                y->left = t;
                t = y;
                if (t->right == NULL)
                    break;
            }
            l->right = t; /* link left */
            l = t;
            t = t->right;
        } else {
            break;
        }
    }
    l->right = t->left; /* assemble */
    r->left = t->right;
    t->left = N.right;
    t->right = N.left;
    return t;
}

static vcdsav_Tree *vcdsav_insert(void *i,
                                  vcdsav_Tree *t,
                                  int val,
                                  unsigned char flags,
                                  GwHistEnt *h)
{
    /* Insert i into the tree t, unless it's already there.    */
    /* Return a pointer to the resulting tree.                 */
    vcdsav_Tree *n;
    int dir;

    n = (vcdsav_Tree *)calloc_2(1, sizeof(vcdsav_Tree));
    if (n == NULL) {
        fprintf(stderr, "vcdsav_insert: ran out of memory, exiting.\n");
        exit(255);
    }
    n->item = i;
    n->val = val;
    n->flags = flags;
    n->hist = h;
    if (t == NULL) {
        n->left = n->right = NULL;
        return n;
    }
    t = vcdsav_splay(i, t);
    dir = vcdsav_cmp_l(i, t->item);
    if (dir < 0) {
        n->left = t->left;
        n->right = t;
        t->left = NULL;
        return n;
    } else if (dir > 0) {
        n->right = t->right;
        n->left = t;
        t->right = NULL;
        return n;
    } else { /* We get here if it's already in the tree */
        /* Don't add it again                      */
        free_2(n);
        return t;
    }
}

/************************ heap ************************/

static int hpcmp(vcdsav_Tree *hp1, vcdsav_Tree *hp2)
{
    GwHistEnt *n1 = hp1->hist;
    GwHistEnt *n2 = hp2->hist;
    GwTime t1, t2;

    if (n1)
        t1 = n1->time;
    else
        t1 = MAX_HISTENT_TIME;
    if (n2)
        t2 = n2->time;
    else
        t2 = MAX_HISTENT_TIME;

    if (t1 == t2) {
        return (0);
    } else if (t1 > t2) {
        return (-1);
    } else {
        return (1);
    }
}

static void recurse_build(vcdsav_Tree *vt, vcdsav_Tree ***hp)
{
    if (vt->left)
        recurse_build(vt->left, hp);

    **hp = vt;
    *hp = (*hp) + 1;

    if (vt->right)
        recurse_build(vt->right, hp);
}

/*
 * heapify algorithm...used to grab the next value change
 */
static void heapify(int i, int heap_size)
{
    int l, r;
    int largest;
    vcdsav_Tree *t;
    int maxele = heap_size / 2 - 1; /* points to where heapswaps don't matter anymore */

    for (;;) {
        l = 2 * i + 1;
        r = l + 1;

        if ((l < heap_size) &&
            (hpcmp(GLOBALS->hp_vcd_saver_c_1[l], GLOBALS->hp_vcd_saver_c_1[i]) > 0)) {
            largest = l;
        } else {
            largest = i;
        }
        if ((r < heap_size) &&
            (hpcmp(GLOBALS->hp_vcd_saver_c_1[r], GLOBALS->hp_vcd_saver_c_1[largest]) > 0)) {
            largest = r;
        }

        if (i != largest) {
            t = GLOBALS->hp_vcd_saver_c_1[i];
            GLOBALS->hp_vcd_saver_c_1[i] = GLOBALS->hp_vcd_saver_c_1[largest];
            GLOBALS->hp_vcd_saver_c_1[largest] = t;

            if (largest <= maxele) {
                i = largest;
            } else {
                break;
            }
        } else {
            break;
        }
    }
}

/*
 * mainline
 */
int save_nodes_to_export_generic(FILE *trans_file,
                                 GwTrace *trans_head,
                                 const char *fname,
                                 int export_typ)
{
    GwTrace *t = trans_head ? trans_head : GLOBALS->traces.first;
    int nodecnt = 0;
    vcdsav_Tree *vt = NULL;
    vcdsav_Tree **hp_clone = GLOBALS->hp_vcd_saver_c_1;
    GwNode *n;
    /* ExtNode *e; */
    /* int msi, lsi; */
    int i;
    GwTime prevtime = GW_TIME_CONSTANT(-1);
    time_t walltime;
    struct strace *st = NULL;
    int strace_append = 0;
    int max_len = 1;
    char *row_data = NULL;
    int is_trans = (export_typ == WAVE_EXPORT_TRANS);
    int dumpvars_state = 0;

    if (export_typ == WAVE_EXPORT_TIM) {
        return (do_timfile_save(fname));
    }

    errno = 0;
    if (export_typ != WAVE_EXPORT_TRANS) {
        GLOBALS->f_vcd_saver_c_1 = fopen(fname, "wb");
    } else {
        if (!trans_head) /* scan-build : is programming error to get here */
        {
            return (VCDSAV_FILE_ERROR);
        }
        GLOBALS->f_vcd_saver_c_1 = trans_file;
    }

    if (!GLOBALS->f_vcd_saver_c_1) {
        return (VCDSAV_FILE_ERROR);
    }

    while (t) {
        if (!t->vector) {
            if (t->n.nd) {
                n = t->n.nd;
                if (n->expansion)
                    n = n->expansion->parent;
                vt = vcdsav_splay(n, vt);
                if (!vt || vt->item != n) {
                    unsigned char flags = 0;

                    if (n->head.next)
                        if (n->head.next->next) {
                            flags = n->head.next->next->flags;
                        }

                    vt = vcdsav_insert(n, vt, ++nodecnt, flags, &n->head);
                }
            }
        } else {
            GwBitVector *b = t->n.vec;
            if (b) {
                GwBits *bt = b->bits;
                if (bt) {
                    for (i = 0; i < bt->nnbits; i++) {
                        if (bt->nodes[i]) {
                            n = bt->nodes[i];

                            if (n->expansion)
                                n = n->expansion->parent;
                            vt = vcdsav_splay(n, vt);
                            if (!vt || vt->item != n) {
                                unsigned char flags = 0;

                                if (n->head.next)
                                    if (n->head.next->next) {
                                        flags = n->head.next->next->flags;
                                    }

                                vt = vcdsav_insert(n, vt, ++nodecnt, flags, &n->head);
                            }
                        }
                    }
                }
            }
        }

        if (export_typ == WAVE_EXPORT_TRANS) {
            break;
        }

        if (!strace_append) {
            t = t->t_next;
            if (t)
                continue;
        } else {
            st = st->next;
            t = st ? st->trace : NULL;
            if (t) {
                continue;
            } else {
                swap_strace_contexts();
            }
        }

    strace_concat:
        GLOBALS->strace_ctx =
            &GLOBALS->strace_windows[GLOBALS->strace_current_window = strace_append];
        strace_append++;
        if (strace_append == WAVE_NUM_STRACE_WINDOWS)
            break;

        if (!GLOBALS->strace_ctx->shadow_straces) {
            goto strace_concat;
        }

        swap_strace_contexts();
        st = GLOBALS->strace_ctx->straces;
        t = st ? st->trace : NULL;
        if (!t) {
            swap_strace_contexts();
            goto strace_concat;
        }
    }

    if (!nodecnt)
        return (VCDSAV_EMPTY);

    GwTime global_time_offset = gw_dump_file_get_global_time_offset(GLOBALS->dump_file);
    GwTime time_scale = gw_dump_file_get_time_scale(GLOBALS->dump_file);
    GwTimeDimension time_dimension = gw_dump_file_get_time_dimension(GLOBALS->dump_file);

    /* header */
    if (export_typ != WAVE_EXPORT_TRANS) {
        time(&walltime);
        w32redirect_fprintf(is_trans, GLOBALS->f_vcd_saver_c_1, "$date\n");
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "\t%s",
                            asctime(localtime(&walltime)));
        w32redirect_fprintf(is_trans, GLOBALS->f_vcd_saver_c_1, "$end\n");
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "$version\n\t" WAVE_VERSION_INFO "\n$end\n");
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "$timescale\n\t%d%c%s\n$end\n",
                            (int)time_scale,
                            time_dimension,
                            (time_dimension == 's') ? "" : "s");
        if (global_time_offset != 0) {
            w32redirect_fprintf(is_trans,
                                GLOBALS->f_vcd_saver_c_1,
                                "$timezero\n\t%" GW_TIME_FORMAT "\n$end\n",
                                global_time_offset);
        }
    } else {
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "$comment data_start %p $end\n",
                            (void *)trans_head); /* arbitrary hex identifier */
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "$comment name %s $end\n",
                            trans_head->name ? trans_head->name : "UNKNOWN");
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "$timescale %d%c%s $end\n",
                            (int)time_scale,
                            time_dimension,
                            (time_dimension == 's') ? "" : "s");
        if (global_time_offset != 0) {
            w32redirect_fprintf(is_trans,
                                GLOBALS->f_vcd_saver_c_1,
                                "$timezero %" GW_TIME_FORMAT " $end\n",
                                global_time_offset);
        }

        GwTimeRange *time_range = gw_dump_file_get_time_range(GLOBALS->dump_file);

        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "$comment min_time %" GW_TIME_FORMAT " $end\n",
                            gw_time_range_get_start(time_range) / time_scale);
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "$comment max_time %" GW_TIME_FORMAT " $end\n",
                            gw_time_range_get_end(time_range) / time_scale);
    }

    if (export_typ == WAVE_EXPORT_TRANS) {
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "$comment max_seqn %d $end\n",
                            nodecnt);
        if (t && t->transaction_args) {
            w32redirect_fprintf(is_trans,
                                GLOBALS->f_vcd_saver_c_1,
                                "$comment args \"%s\" $end\n",
                                t->transaction_args);
        }
    }

    /* write out netnames here ... */
    hp_clone = GLOBALS->hp_vcd_saver_c_1 = calloc_2(nodecnt, sizeof(vcdsav_Tree *));
    recurse_build(vt, &hp_clone);

    for (i = 0; i < nodecnt; i++) {
        char *hname = GLOBALS->hp_vcd_saver_c_1[i]->item->nname;
        char *netname = output_hier(is_trans, hname);

        if (export_typ == WAVE_EXPORT_TRANS) {
            w32redirect_fprintf(is_trans,
                                GLOBALS->f_vcd_saver_c_1,
                                "$comment seqn %d %s $end\n",
                                GLOBALS->hp_vcd_saver_c_1[i]->val,
                                hname);
        }

        if (GLOBALS->hp_vcd_saver_c_1[i]->flags &
            (GW_HIST_ENT_FLAG_REAL | GW_HIST_ENT_FLAG_STRING)) {
            const char *typ =
                (GLOBALS->hp_vcd_saver_c_1[i]->flags & GW_HIST_ENT_FLAG_STRING) ? "string" : "real";
            int tlen = (GLOBALS->hp_vcd_saver_c_1[i]->flags & GW_HIST_ENT_FLAG_STRING) ? 0 : 1;
            w32redirect_fprintf(is_trans,
                                GLOBALS->f_vcd_saver_c_1,
                                "$var %s %d %s %s $end\n",
                                typ,
                                tlen,
                                vcdid(GLOBALS->hp_vcd_saver_c_1[i]->val, export_typ),
                                netname);
        } else {
            int msi = -1, lsi = -1;

            if (GLOBALS->hp_vcd_saver_c_1[i]->item->extvals) {
                msi = GLOBALS->hp_vcd_saver_c_1[i]->item->msi;
                lsi = GLOBALS->hp_vcd_saver_c_1[i]->item->lsi;
            }

            if (msi == lsi) {
                w32redirect_fprintf(
                    is_trans,
                    GLOBALS->f_vcd_saver_c_1,
                    "$var %s 1 %s %s $end\n",
                    gw_var_type_to_string(GLOBALS->hp_vcd_saver_c_1[i]->item->vartype),
                    vcdid(GLOBALS->hp_vcd_saver_c_1[i]->val, export_typ),
                    netname);
            } else {
                int len = (msi < lsi) ? (lsi - msi + 1) : (msi - lsi + 1);
                w32redirect_fprintf(
                    is_trans,
                    GLOBALS->f_vcd_saver_c_1,
                    "$var %s %d %s %s $end\n",
                    gw_var_type_to_string(GLOBALS->hp_vcd_saver_c_1[i]->item->vartype),
                    len,
                    vcdid(GLOBALS->hp_vcd_saver_c_1[i]->val, export_typ),
                    netname);
                GLOBALS->hp_vcd_saver_c_1[i]->len = len;
                if (len > max_len)
                    max_len = len;
            }
        }

        /* if(was_packed) { free_2(hname); } ...not needed for HIER_DEPACK_STATIC */
    }

    row_data = malloc_2(max_len + 1);

    output_hier(is_trans, "");
    free_hier();

    w32redirect_fprintf(is_trans, GLOBALS->f_vcd_saver_c_1, "$enddefinitions $end\n");

    /* value changes */

    for (i = (nodecnt / 2 - 1); i > 0; i--) /* build nodes into having heap property */
    {
        heapify(i, nodecnt);
    }

    GwTimeRange *time_range = gw_dump_file_get_time_range(GLOBALS->dump_file);

    for (;;) {
        heapify(0, nodecnt);

        if (!GLOBALS->hp_vcd_saver_c_1[0]->hist)
            break;
        if (GLOBALS->hp_vcd_saver_c_1[0]->hist->time > gw_time_range_get_end(time_range))
            break;

        if ((GLOBALS->hp_vcd_saver_c_1[0]->hist->time != prevtime) &&
            (GLOBALS->hp_vcd_saver_c_1[0]->hist->time >= GW_TIME_CONSTANT(0))) {
            GwTime tnorm = GLOBALS->hp_vcd_saver_c_1[0]->hist->time;
            if (time_scale != 1) {
                tnorm /= time_scale;
            }

            if (dumpvars_state == 1) {
                w32redirect_fprintf(is_trans, GLOBALS->f_vcd_saver_c_1, "$end\n");
                dumpvars_state = 2;
            }
            w32redirect_fprintf(is_trans,
                                GLOBALS->f_vcd_saver_c_1,
                                "#%" GW_TIME_FORMAT "\n",
                                tnorm);
            if (!dumpvars_state) {
                w32redirect_fprintf(is_trans, GLOBALS->f_vcd_saver_c_1, "$dumpvars\n");
                dumpvars_state = 1;
            }
            prevtime = GLOBALS->hp_vcd_saver_c_1[0]->hist->time;
        }

        if (GLOBALS->hp_vcd_saver_c_1[0]->hist->time >= GW_TIME_CONSTANT(0)) {
            if (GLOBALS->hp_vcd_saver_c_1[0]->flags &
                (GW_HIST_ENT_FLAG_REAL | GW_HIST_ENT_FLAG_STRING)) {
                if (GLOBALS->hp_vcd_saver_c_1[0]->flags & GW_HIST_ENT_FLAG_STRING) {
                    char *vec = GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_vector
                                    ? GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_vector
                                    : "UNDEF";

                    int vec_slen = strlen(vec);
                    char *vec_escaped = malloc_2(vec_slen * 4 + 1); /* worst case */
                    int vlen = fstUtilityBinToEsc((unsigned char *)vec_escaped,
                                                  (unsigned char *)vec,
                                                  vec_slen);

                    vec_escaped[vlen] = 0;
                    if (vlen) {
                        w32redirect_fprintf(is_trans,
                                            GLOBALS->f_vcd_saver_c_1,
                                            "s%s %s\n",
                                            vec_escaped,
                                            vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
                    } else {
                        w32redirect_fprintf(is_trans,
                                            GLOBALS->f_vcd_saver_c_1,
                                            "s\\000 %s\n",
                                            vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
                    }
                    free_2(vec_escaped);
                } else {
                    double *d = &GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_double;
                    double value;

                    if (!d) {
                        sscanf("NaN", "%lg", &value);
                    } else {
                        value = *d;
                    }

                    w32redirect_fprintf(is_trans,
                                        GLOBALS->f_vcd_saver_c_1,
                                        "r%.16g %s\n",
                                        value,
                                        vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
                }
            } else if (GLOBALS->hp_vcd_saver_c_1[0]->len) {
                if (GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_vector) {
                    for (i = 0; i < GLOBALS->hp_vcd_saver_c_1[0]->len; i++) {
                        row_data[i] =
                            analyzer_demang(0, GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_vector[i]);
                    }
                } else {
                    for (i = 0; i < GLOBALS->hp_vcd_saver_c_1[0]->len; i++) {
                        row_data[i] = 'x';
                    }
                }
                row_data[i] = 0;

                w32redirect_fprintf(is_trans,
                                    GLOBALS->f_vcd_saver_c_1,
                                    "b%s %s\n",
                                    vcd_truncate_bitvec(row_data),
                                    vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
            } else {
                w32redirect_fprintf(is_trans,
                                    GLOBALS->f_vcd_saver_c_1,
                                    "%c%s\n",
                                    analyzer_demang(0, GLOBALS->hp_vcd_saver_c_1[0]->hist->v.h_val),
                                    vcdid(GLOBALS->hp_vcd_saver_c_1[0]->val, export_typ));
            }
        }

        GLOBALS->hp_vcd_saver_c_1[0]->hist = GLOBALS->hp_vcd_saver_c_1[0]->hist->next;
    }

    if (prevtime < gw_time_range_get_end(time_range)) {
        if (dumpvars_state == 1) {
            w32redirect_fprintf(is_trans, GLOBALS->f_vcd_saver_c_1, "$end\n");
            dumpvars_state = 2;
        }
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "#%" GW_TIME_FORMAT "\n",
                            gw_time_range_get_end(time_range) / time_scale);
    }

    for (i = 0; i < nodecnt; i++) {
        free_2(GLOBALS->hp_vcd_saver_c_1[i]);
    }

    free_2(GLOBALS->hp_vcd_saver_c_1);
    GLOBALS->hp_vcd_saver_c_1 = NULL;
    free_2(row_data);
    row_data = NULL;

    if (export_typ != WAVE_EXPORT_TRANS) {
        fclose(GLOBALS->f_vcd_saver_c_1);
    } else {
        w32redirect_fprintf(is_trans,
                            GLOBALS->f_vcd_saver_c_1,
                            "$comment data_end %p $end\n",
                            (void *)trans_head); /* arbitrary hex identifier */
#if !defined __MINGW32__
        fflush(GLOBALS->f_vcd_saver_c_1);
#endif
    }

    GLOBALS->f_vcd_saver_c_1 = NULL;

    return (VCDSAV_OK);
}

int save_nodes_to_export(const char *fname, int export_typ)
{
    return (save_nodes_to_export_generic(NULL, NULL, fname, export_typ));
}

int save_nodes_to_trans(FILE *trans, GwTrace *t)
{
    return (save_nodes_to_export_generic(trans, t, NULL, WAVE_EXPORT_TRANS));
}

/************************ scopenav ************************/

struct namehier
{
    struct namehier *next;
    char *name;
    char not_final;
};

void free_hier(void)
{
    struct namehier *nhtemp;

    while (GLOBALS->nhold_vcd_saver_c_1) {
        nhtemp = GLOBALS->nhold_vcd_saver_c_1->next;
        free_2(GLOBALS->nhold_vcd_saver_c_1->name);
        free_2(GLOBALS->nhold_vcd_saver_c_1);
        GLOBALS->nhold_vcd_saver_c_1 = nhtemp;
    }
}

/*
 * navigate up and down the scope hierarchy and
 * emit the appropriate vcd scope primitives
 */
static void diff_hier(int is_trans, struct namehier *nh1, struct namehier *nh2)
{
    /* struct namehier *nhtemp; */ /* scan-build */

    if (!nh2) {
        while ((nh1) && (nh1->not_final)) {
            w32redirect_fprintf(is_trans,
                                GLOBALS->f_vcd_saver_c_1,
                                "$scope module %s $end\n",
                                nh1->name);
            nh1 = nh1->next;
        }
        return;
    }

    for (;;) {
        if ((nh1->not_final == 0) && (nh2->not_final == 0)) /* both are equal */
        {
            break;
        }

        if (nh2->not_final == 0) /* old hier is shorter */
        {
            /* nhtemp=nh1; */ /* scan-build */
            while ((nh1) && (nh1->not_final)) {
                w32redirect_fprintf(is_trans,
                                    GLOBALS->f_vcd_saver_c_1,
                                    "$scope module %s $end\n",
                                    nh1->name);
                nh1 = nh1->next;
            }
            break;
        }

        if (nh1->not_final == 0) /* new hier is shorter */
        {
            /* nhtemp=nh2; */ /* scan-build */
            while ((nh2) && (nh2->not_final)) {
                w32redirect_fprintf(is_trans, GLOBALS->f_vcd_saver_c_1, "$upscope $end\n");
                nh2 = nh2->next;
            }
            break;
        }

        if (strcmp(nh1->name, nh2->name)) {
            /* nhtemp=nh2; */ /* prune old hier */ /* scan-build */
            while ((nh2) && (nh2->not_final)) {
                w32redirect_fprintf(is_trans, GLOBALS->f_vcd_saver_c_1, "$upscope $end\n");
                nh2 = nh2->next;
            }

            /* nhtemp=nh1; */ /* add new hier */ /* scan-build */
            while ((nh1) && (nh1->not_final)) {
                w32redirect_fprintf(is_trans,
                                    GLOBALS->f_vcd_saver_c_1,
                                    "$scope module %s $end\n",
                                    nh1->name);
                nh1 = nh1->next;
            }
            break;
        }

        nh1 = nh1->next;
        nh2 = nh2->next;
    }
}

/*
 * output scopedata for a given name if needed, return pointer to name string
 */
char *output_hier(int is_trans, const char *name)
{
    const char *pnt;
    const char *pnt2;
    char *s;
    int len;
    struct namehier *nh_head = NULL;
    struct namehier *nh_curr = NULL;
    struct namehier *nhtemp;

    pnt = pnt2 = name;

    for (;;) {
        if (*pnt2 == '\\') {
            while (*pnt2)
                pnt2++;
        } else {
            /* while((*pnt2!='.')&&(*pnt2)) pnt2++; ... does not handle dot at end of name */

            while (*pnt2) {
                if (*pnt2 != '.') {
                    pnt2++;
                    continue;
                } else {
                    if (!*(pnt2 + 1)) /* if dot is at end of name */
                    {
                        pnt2++;
                        continue;
                    } else {
                        break;
                    }
                }
            }
        }

        s = (char *)calloc_2(1, (len = pnt2 - pnt) + 1);
        memcpy(s, pnt, len);
        nhtemp = (struct namehier *)calloc_2(1, sizeof(struct namehier));
        nhtemp->name = s;

        if (!nh_curr) {
            nh_head = nh_curr = nhtemp;
        } else {
            nh_curr->next = nhtemp;
            nh_curr->not_final = 1;
            nh_curr = nhtemp;
        }

        if (!*pnt2)
            break;
        pnt = (++pnt2);
    }

    diff_hier(is_trans, nh_head, GLOBALS->nhold_vcd_saver_c_1);
    free_hier();
    GLOBALS->nhold_vcd_saver_c_1 = nh_head;

    {
        char *mti_sv_patch =
            strstr(nh_curr->name, "]["); /* case is: #implicit-var###VarElem:ram_di[0.0] [63:0] */
        if (mti_sv_patch) {
            char *t = calloc_2(1, strlen(nh_curr->name) + 1 + 1);

            *mti_sv_patch = 0;
            sprintf(t, "%s] %s", nh_curr->name, mti_sv_patch + 1);

            free_2(nh_curr->name);
            nh_curr->name = t;
        }

        if ((nh_curr->name[0] == '\\') && (nh_curr->name[1] == '#')) {
            return (nh_curr->name + 1);
        }
    }

    return (nh_curr->name);
}

/****************************************/
/***                                  ***/
/*** output in timing analyzer format ***/
/***                                  ***/
/****************************************/

static void write_hptr_trace(GwTrace *t, int *whichptr, GwTime tmin, GwTime tmax)
{
    GwNode *n = t->n.nd;
    GwHistEnt **ha = n->harray;
    int numhist = n->numhist;
    int i;
    unsigned char h_val = GW_BIT_X;
    gboolean first;
    gboolean invert = ((t->flags & TR_INVERT) != 0);
    int edges = 0;

    first = TRUE;
    for (i = 0; i < numhist; i++) {
        if (ha[i]->time < tmin) {
        } else if (ha[i]->time > tmax) {
            break;
        } else {
            if ((ha[i]->time != tmin) || (!first)) {
                edges++;
            }

            first = FALSE;
        }
    }

    first = TRUE;
    for (i = 0; i < numhist; i++) {
        if (ha[i]->time < tmin) {
            h_val = invert ? AN_USTR_INV[ha[i]->v.h_val] : AN_USTR[ha[i]->v.h_val];
        } else if (ha[i]->time > tmax) {
            break;
        } else {
            if (first) {
                gboolean skip_this = (ha[i]->time == tmin);

                if (skip_this) {
                    h_val = invert ? AN_USTR_INV[ha[i]->v.h_val] : AN_USTR[ha[i]->v.h_val];
                }

                w32redirect_fprintf(0,
                                    GLOBALS->f_vcd_saver_c_1,
                                    "Digital_Signal\n"
                                    "     Position:          %d\n"
                                    "     Height:            24\n"
                                    "     Space_Above:       24\n"
                                    "     Name:              %s\n"
                                    "     Start_State:       %c\n"
                                    "     Number_Edges:      %d\n"
                                    "     Rise_Time:         0.2\n"
                                    "     Fall_Time:         0.2\n",
                                    *whichptr,
                                    t->name,
                                    h_val,
                                    edges);
                first = FALSE;

                if (skip_this) {
                    continue;
                }
            }

            h_val = invert ? AN_USTR_INV[ha[i]->v.h_val] : AN_USTR[ha[i]->v.h_val];
            w32redirect_fprintf(0,
                                GLOBALS->f_vcd_saver_c_1,
                                "          Edge:               %" GW_TIME_FORMAT ".0 %c\n",
                                ha[i]->time,
                                h_val);
        }
    }

    if (first) {
        /* need to emit blank trace */
        w32redirect_fprintf(0,
                            GLOBALS->f_vcd_saver_c_1,
                            "Digital_Signal\n"
                            "     Position:          %d\n"
                            "     Height:            24\n"
                            "     Space_Above:       24\n"
                            "     Name:              %s\n"
                            "     Start_State:       %c\n"
                            "     Number_Edges:      %d\n"
                            "     Rise_Time:         10.0\n"
                            "     Fall_Time:         10.0\n",
                            *whichptr,
                            t->name,
                            h_val,
                            edges);
    }

    (*whichptr)++;
}

/***/

static void format_value_string(char *s)
{
    char *s_orig = s;

    if ((s) && (*s)) {
        gboolean is_all_z = TRUE;
        gboolean is_all_x = TRUE;

        while (*s) {
            if ((*s != 'z') && (*s != 'Z')) {
                is_all_z = FALSE;
            }
            if ((*s != 'x') && (*s != 'X')) {
                is_all_x = FALSE;
            }

            /* if(isspace(*s)) *s='_'; ...not needed */
            s++;
        }

        if (is_all_z) {
            *(s_orig++) = 'Z';
            *(s_orig) = 0;
        } else if (is_all_x) {
            *(s_orig++) = 'X';
            *(s_orig) = 0;
        }
    }
}

static char *get_hptr_vector_val(GwTrace *t, GwHistEnt *h)
{
    char *ascii = NULL;

    if (h->time < GW_TIME_CONSTANT(0)) {
        ascii = strdup_2("X");
    } else if (h->flags & GW_HIST_ENT_FLAG_REAL) {
        if (!(h->flags & GW_HIST_ENT_FLAG_STRING)) {
            ascii = convert_ascii_real(t, &h->v.h_double);
        } else {
            ascii = convert_ascii_string((char *)h->v.h_vector);
        }
    } else {
        ascii = convert_ascii_vec(t, h->v.h_vector);
    }

    format_value_string(ascii);
    return (ascii);
}

static const char *vcdsav_dtypes[] = {"Bin", "Hex", "Text"};

static int determine_trace_data_type(char *s, int curtype)
{
    int i;

    if ((s) && (curtype != VCDSAV_IS_TEXT)) {
        int len = strlen(s);
        for (i = 0; i < len; i++) {
            int ch = (int)((unsigned char)s[i]);

            if (strchr("01xXzZhHuUwWlL-?_", ch)) {
                /* nothing */
            } else if (strchr("23456789aAbBcCdDeEfF", ch)) {
                curtype = VCDSAV_IS_HEX;
            } else {
                curtype = VCDSAV_IS_TEXT;
                break;
            }
        }
    }

    return (curtype);
}

static void write_hptr_trace_vector(GwTrace *t, int *whichptr, GwTime tmin, GwTime tmax)
{
    GwNode *n = t->n.nd;
    GwHistEnt **ha = n->harray;
    int numhist = n->numhist;
    int i;
    char *h_val = NULL;
    gboolean first;
    int edges = 0;
    int curtype = VCDSAV_IS_BIN;

    first = TRUE;
    for (i = 0; i < numhist; i++) {
        if (ha[i]->time < tmin) {
        } else if (ha[i]->time > tmax) {
            break;
        } else {
            char *s = get_hptr_vector_val(t, ha[i]);
            if (s) {
                curtype = determine_trace_data_type(s, curtype);
                free_2(s);
            }

            if ((ha[i]->time != tmin) || (!first)) {
                edges++;
            }

            first = FALSE;
        }
    }

    first = TRUE;
    for (i = 0; i < numhist; i++) {
        if (ha[i]->time < tmin) {
            if (h_val)
                free_2(h_val);
            h_val = get_hptr_vector_val(t, ha[i]);
        } else if (ha[i]->time > tmax) {
            break;
        } else {
            if (first) {
                gboolean skip_this = (ha[i]->time == tmin);

                if (skip_this) {
                    if (h_val)
                        free_2(h_val);
                    h_val = get_hptr_vector_val(t, ha[i]);
                }

                w32redirect_fprintf(0,
                                    GLOBALS->f_vcd_saver_c_1,
                                    "Digital_Bus\n"
                                    "     Position:          %d\n"
                                    "     Height:            24\n"
                                    "     Space_Above:       24\n"
                                    "     Name:              %s\n"
                                    "     Start_State:       %s\n"
                                    "     State_Format:      %s\n"
                                    "     Number_Edges:      %d\n"
                                    "     Rise_Time:         0.2\n"
                                    "     Fall_Time:         0.2\n",
                                    *whichptr,
                                    t->name,
                                    h_val,
                                    vcdsav_dtypes[curtype],
                                    edges);
                first = FALSE;

                if (skip_this) {
                    continue;
                }
            }

            if (h_val)
                free_2(h_val);
            h_val = get_hptr_vector_val(t, ha[i]);
            w32redirect_fprintf(0,
                                GLOBALS->f_vcd_saver_c_1,
                                "          Edge:               %" GW_TIME_FORMAT ".0 %s\n",
                                ha[i]->time,
                                h_val);
        }
    }

    if (first) {
        /* need to emit blank trace */
        w32redirect_fprintf(0,
                            GLOBALS->f_vcd_saver_c_1,
                            "Digital_Bus\n"
                            "     Position:          %d\n"
                            "     Height:            24\n"
                            "     Space_Above:       24\n"
                            "     Name:              %s\n"
                            "     Start_State:       %s\n"
                            "     State_Format:      %s\n"
                            "     Number_Edges:      %d\n"
                            "     Rise_Time:         10.0\n"
                            "     Fall_Time:         10.0\n",
                            *whichptr,
                            t->name,
                            h_val,
                            vcdsav_dtypes[curtype],
                            edges);
    }

    if (h_val)
        free_2(h_val);
    (*whichptr)++;
}

/***/

static char *get_vptr_vector_val(GwTrace *t, GwVectorEnt *v)
{
    char *ascii = NULL;

    if (v->time < GW_TIME_CONSTANT(0)) {
        ascii = strdup_2("X");
    } else {
        ascii = convert_ascii(t, v);
    }

    if (!ascii) {
        ascii = strdup_2("X");
    }

    format_value_string(ascii);
    return (ascii);
}

static void write_vptr_trace(GwTrace *t, int *whichptr, GwTime tmin, GwTime tmax)
{
    GwVectorEnt **ha = t->n.vec->vectors;
    int numhist = t->n.vec->numregions;
    int i;
    char *h_val = NULL;
    gboolean first;
    int edges = 0;
    int curtype = VCDSAV_IS_BIN;

    first = TRUE;
    for (i = 0; i < numhist; i++) {
        if (ha[i]->time < tmin) {
        } else if (ha[i]->time > tmax) {
            break;
        } else {
            char *s = get_vptr_vector_val(t, ha[i]);
            if (s) {
                curtype = determine_trace_data_type(s, curtype);
                free_2(s);
            }

            if ((ha[i]->time != tmin) || (!first)) {
                edges++;
            }

            first = FALSE;
        }
    }

    first = TRUE;
    for (i = 0; i < numhist; i++) {
        if (ha[i]->time < tmin) {
            if (h_val)
                free_2(h_val);
            h_val = get_vptr_vector_val(t, ha[i]);
        } else if (ha[i]->time > tmax) {
            break;
        } else {
            if (first) {
                gboolean skip_this = (ha[i]->time == tmin);

                if (skip_this) {
                    if (h_val)
                        free_2(h_val);
                    h_val = get_vptr_vector_val(t, ha[i]);
                }

                w32redirect_fprintf(0,
                                    GLOBALS->f_vcd_saver_c_1,
                                    "Digital_Bus\n"
                                    "     Position:          %d\n"
                                    "     Height:            24\n"
                                    "     Space_Above:       24\n"
                                    "     Name:              %s\n"
                                    "     Start_State:       %s\n"
                                    "     State_Format:      %s\n"
                                    "     Number_Edges:      %d\n"
                                    "     Rise_Time:         0.2\n"
                                    "     Fall_Time:         0.2\n",
                                    *whichptr,
                                    t->name,
                                    h_val,
                                    vcdsav_dtypes[curtype],
                                    edges);
                first = FALSE;

                if (skip_this) {
                    continue;
                }
            }

            if (h_val)
                free_2(h_val);
            h_val = get_vptr_vector_val(t, ha[i]);
            w32redirect_fprintf(0,
                                GLOBALS->f_vcd_saver_c_1,
                                "          Edge:               %" GW_TIME_FORMAT ".0 %s\n",
                                ha[i]->time,
                                h_val);
        }
    }

    if (first) {
        /* need to emit blank trace */
        w32redirect_fprintf(0,
                            GLOBALS->f_vcd_saver_c_1,
                            "Digital_Bus\n"
                            "     Position:          %d\n"
                            "     Height:            24\n"
                            "     Space_Above:       24\n"
                            "     Name:              %s\n"
                            "     Start_State:       %s\n"
                            "     State_Format:      %s\n"
                            "     Number_Edges:      %d\n"
                            "     Rise_Time:         10.0\n"
                            "     Fall_Time:         10.0\n",
                            *whichptr,
                            t->name,
                            h_val,
                            vcdsav_dtypes[curtype],
                            edges);
    }

    if (h_val)
        free_2(h_val);
    (*whichptr)++;
}

static void write_tim_tracedata(GwTrace *t, int *whichptr, GwTime tmin, GwTime tmax)
{
    if (!(t->flags & (TR_EXCLUDE | TR_BLANK | TR_ANALOG_BLANK_STRETCH))) {
        GLOBALS->shift_timebase = t->shift;
        if (!t->vector) {
            if (!t->n.nd->extvals) {
                write_hptr_trace(t, whichptr, tmin, tmax); /* single-bit */
            } else {
                write_hptr_trace_vector(t, whichptr, tmin, tmax); /* multi-bit */
            }
        } else {
            write_vptr_trace(t, whichptr, tmin, tmax); /* synthesized/concatenated vector */
        }
    }
}

int do_timfile_save(const char *fname)
{
    const char *time_prefix = WAVE_SI_UNITS;
    const double negpow[] = {1.0, 1.0e-3, 1.0e-6, 1.0e-9, 1.0e-12, 1.0e-15, 1.0e-18, 1.0e-21};
    char *pnt;
    int offset;
    GwTrace *t = GLOBALS->traces.first;
    int i = 1; /* trace index in the .tim file */
    GwTime tmin, tmax;

    errno = 0;

    GwMarker *primary_marker = gw_project_get_primary_marker(GLOBALS->project);
    GwMarker *baseline_marker = gw_project_get_baseline_marker(GLOBALS->project);
    GwTimeRange *time_range = gw_dump_file_get_time_range(GLOBALS->dump_file);

    if (gw_marker_is_enabled(primary_marker) && gw_marker_is_enabled(baseline_marker)) {
        GwTime primary_pos = gw_marker_get_position(primary_marker);
        GwTime baseline_pos = gw_marker_get_position(baseline_marker);

        tmin = MIN(primary_pos, baseline_pos);
        tmax = MAX(primary_pos, baseline_pos);
    } else {
        tmin = gw_time_range_get_start(time_range);
        tmax = gw_time_range_get_end(time_range);
    }

    GLOBALS->f_vcd_saver_c_1 = fopen(fname, "wb");
    if (!GLOBALS->f_vcd_saver_c_1) {
        return (VCDSAV_FILE_ERROR);
    }

    GwTimeDimension time_dimension = gw_dump_file_get_time_dimension(GLOBALS->dump_file);
    pnt = strchr(time_prefix, (int)time_dimension);
    if (pnt) {
        offset = pnt - time_prefix;
    } else
        offset = 0;

    w32redirect_fprintf(0,
                        GLOBALS->f_vcd_saver_c_1,
                        "Timing Analyzer Settings\n"
                        "     Time_Scale:        %E\n"
                        "     Time_Per_Division: %E\n"
                        "     NumberDivisions:   10\n"
                        "     Start_Time:        %" GW_TIME_FORMAT ".0\n"
                        "     End_Time:          %" GW_TIME_FORMAT ".0\n",

                        negpow[offset],
                        (tmax - tmin) / 10.0,
                        tmin,
                        tmax);

    while (t) {
        write_tim_tracedata(t, &i, tmin, tmax);
        t = GiveNextTrace(t);
    }

    fclose(GLOBALS->f_vcd_saver_c_1);
    GLOBALS->f_vcd_saver_c_1 = NULL;

    return (errno ? VCDSAV_FILE_ERROR : VCDSAV_OK);
}
